﻿using IOP.OpenType.Tables;
using System;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Concurrent;
using IOP.OpenType;

namespace IOP.Codec.OpenType
{
    /// <summary>
    /// TTF文件解码器
    /// </summary>
    public partial class OTFDecoder : IDisposable
    {
        /// <summary>
        /// 文件信息
        /// </summary>
        public FileInfo FileInfo { get; private set; }

        /// <summary>
        /// 偏移表
        /// </summary>
        public OffsetTable OffsetTable { get; private set; }
        /// <summary>
        /// 表字典
        /// </summary>
        protected ConcurrentDictionary<string, Table> _Tables = new ConcurrentDictionary<string, Table>();
        /// <summary>
        /// 字形表
        /// </summary>
        protected GlyfTable GlyfTable = new GlyfTable();

        /// <summary>
        /// 信号灯
        /// </summary>
        private readonly SemaphoreSlim _SemaphoreSlim = new SemaphoreSlim(1, 1);

        private bool _IsDisable = false;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="path"></param>
        public OTFDecoder(string path)
        {
            FileInfo = new FileInfo(path);
            if (!FileInfo.Exists) throw new FileNotFoundException($"Cannot found file with {path}");
            if (FileInfo.Extension != ".ttf" && FileInfo.Extension != ".otf") throw new NotSupportedException("Target file is not ttf file");
        }

        /// <summary>
        /// 读取偏移表
        /// </summary>
        /// <returns></returns>
        public async Task<OffsetTable> ReadOffsetTable()
        {
            if (OffsetTable != null) return OffsetTable;
            await _SemaphoreSlim.WaitAsync();
            try
            {
                if (OffsetTable != null) return OffsetTable;
                using FileStream stream = new FileStream(FileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
                OffsetTableLoader loader = new OffsetTableLoader();
                var table = await loader.Load(stream);
                OffsetTable = table;
                return table;
            }
            finally
            {
                _SemaphoreSlim.Release();
            }
        }
        /// <summary>
        /// 读取头表
        /// </summary>
        /// <returns></returns>
        public async Task<HeadTable> ReadHeadTable()
        {
            if (OffsetTable == null) await ReadOffsetTable();
            TableEntry entry = GetEntry(TableNames.Head);
            if(_Tables.TryGetValue(entry.Tag, out Table head))
            {
                HeadTable headTable = head as HeadTable;
                return headTable;
            }
            await _SemaphoreSlim.WaitAsync();
            try
            {
                if (_Tables.TryGetValue(entry.Tag, out head))
                {
                    HeadTable h = head as HeadTable;
                    return h;
                }
                using FileStream stream = new FileStream(FileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
                HeadTableLoader loader = new HeadTableLoader();
                var table = await loader.Load(stream, entry);
                _Tables.AddOrUpdate(entry.Tag, table, (key, value) => value);
                return table;
            }
            finally
            {
                _SemaphoreSlim.Release();
            }
        }
        /// <summary>
        /// 读取最大配置表
        /// </summary>
        /// <returns></returns>
        public async Task<MaxpTable> ReadMaxpTable()
        {
            if (OffsetTable == null) await ReadOffsetTable();
            TableEntry entry = GetEntry(TableNames.Maxp);
            if (_Tables.TryGetValue(entry.Tag, out Table table))
            {
                MaxpTable maxp = table as MaxpTable;
                return maxp;
            }
            await _SemaphoreSlim.WaitAsync();
            try
            {
                if (_Tables.TryGetValue(entry.Tag, out table))
                {
                    MaxpTable m = table as MaxpTable;
                    return m;
                }
                using FileStream stream = new FileStream(FileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
                MaxpTableLoader loader = new MaxpTableLoader();
                var maxp = await loader.Load(stream, entry);
                _Tables.AddOrUpdate(entry.Tag, maxp, (key, value) => value);
                return maxp;
            }
            finally
            {
                _SemaphoreSlim.Release();
            }
        }
        /// <summary>
        /// 读取字符映射表
        /// </summary>
        /// <returns></returns>
        public async Task<CmapTable> ReadCmapTable()
        {
            if (OffsetTable == null) await ReadOffsetTable();
            TableEntry entry = GetEntry(TableNames.Cmap);
            if (_Tables.TryGetValue(entry.Tag, out Table table))
            {
                CmapTable cmap = table as CmapTable;
                return cmap;
            }
            await _SemaphoreSlim.WaitAsync();
            try
            {
                if (_Tables.TryGetValue(entry.Tag, out table))
                {
                    CmapTable c = table as CmapTable;
                    return c;
                }
                using FileStream stream = new FileStream(FileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
                CmapTableLoader loader = new CmapTableLoader();
                var cmap = await loader.Load(stream, entry);
                _Tables.AddOrUpdate(entry.Tag, cmap, (key, value) => value);
                return cmap;
            }
            finally
            {
                _SemaphoreSlim.Release();
            }
        }
        /// <summary>
        /// 读取本地索引表
        /// </summary>
        /// <returns></returns>
        public async Task<LocaTable> ReadLocaTable()
        {
            if (OffsetTable == null) 
                await ReadOffsetTable();
            var head = await ReadHeadTable();
            var maxp = await ReadMaxpTable();
            TableEntry entry = GetEntry(TableNames.Loca);
            TableEntry glyph = GetEntry(TableNames.Glyf);
            if (_Tables.TryGetValue(entry.Tag, out Table table))
            {
                LocaTable loca = table as LocaTable;
                return loca;
            }
            await _SemaphoreSlim.WaitAsync();
            try
            {
                if (_Tables.TryGetValue(entry.Tag, out table))
                {
                    LocaTable l = table as LocaTable;
                    return l;
                }
                using FileStream stream = new FileStream(FileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
                LocaTableLoader loader = new LocaTableLoader();
                var loca = await loader.Load(stream, entry, head.IndexToLocFormat, maxp.NumGlyphs, glyph.Length);
                _Tables.AddOrUpdate(entry.Tag, loca, (key, value) => value);
                return loca;
            }
            finally
            {
                _SemaphoreSlim.Release();
            }
        }
        /// <summary>
        /// 读取横向度量表
        /// </summary>
        /// <returns></returns>
        public async Task<HheaTable> ReadHheaTable()
        {
            if (OffsetTable == null)
                await ReadOffsetTable();
            TableEntry entry = GetEntry(TableNames.Hhea);
            if (_Tables.TryGetValue(entry.Tag, out Table table))
            {
                HheaTable h = table as HheaTable;
                return h;
            }
            await _SemaphoreSlim.WaitAsync();
            try
            {
                if (_Tables.TryGetValue(entry.Tag, out table))
                {
                    HheaTable h = table as HheaTable;
                    return h;
                }
                using FileStream stream = new FileStream(FileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
                HheaTableLoader loader = new HheaTableLoader();
                var hheaTable = await loader.Load(stream, entry);
                _Tables.AddOrUpdate(entry.Tag, hheaTable, (key, value) => value);
                return hheaTable;
            }
            finally
            {
                _SemaphoreSlim.Release();
            }
        }
        /// <summary>
        /// 读取横向度量表
        /// </summary>
        /// <returns></returns>
        public async Task<HmtxTable> ReadHmtxTable()
        {
            if (OffsetTable == null)
                await ReadOffsetTable();
            TableEntry entry = GetEntry(TableNames.Hmtx);
            var maxp = await ReadMaxpTable();
            var hhea = await ReadHheaTable();
            if (_Tables.TryGetValue(entry.Tag, out Table table))
            {
                HmtxTable h = table as HmtxTable;
                return h;
            }
            await _SemaphoreSlim.WaitAsync();
            try
            {
                if (_Tables.TryGetValue(entry.Tag, out table))
                {
                    HmtxTable h = table as HmtxTable;
                    return h;
                }
                using FileStream stream = new FileStream(FileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
                HmtxTableLoader loader = new HmtxTableLoader();
                var hmtxTable = await loader.Load(stream, entry, maxp.NumGlyphs, hhea.NumberOfHMetrics);
                _Tables.AddOrUpdate(entry.Tag, hmtxTable, (key, value) => value);
                return hmtxTable;
            }
            finally
            {
                _SemaphoreSlim.Release();
            }
        }

        /// <summary>
        /// 获取字形原始数据
        /// </summary>
        /// <param name="c"></param>
        /// <param name="platformID"></param>
        /// <param name="encodingID"></param>
        /// <returns></returns>
        public async Task<GlyphDescription> ReadGlyfData(char c, ushort platformID = 3, ushort encodingID = 1)
        {
            var loca = await ReadLocaTable();
            var cmap = await ReadCmapTable();
            TableEntry entry = GetEntry(TableNames.Glyf);
            uint gId = cmap.GetGlyphId(platformID, encodingID, c);
            if(!GlyfTable.Descriptions.TryGetValue(gId,out GlyphDescription description))
            {
                (uint offset, uint length) = loca.GetOffestAndLengthFromGlyphId(gId);
                using FileStream stream = new FileStream(FileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
                GlyfTableLoader loader = new GlyfTableLoader();
                description = await loader.LoadGlyfData(stream, entry, offset, length);
                GlyfTable.Descriptions.AddOrUpdate(gId, description, (key, value) => value);
            }
            return description;
        }
        /// <summary>
        /// 通过Gid获取字形数据
        /// </summary>
        /// <param name="gid"></param>
        /// <returns></returns>
        public async Task<GlyphDescription> ReadGlyfData(uint gid)
        {
            var loca = await ReadLocaTable();
            TableEntry entry = GetEntry(TableNames.Glyf);
            if (!GlyfTable.Descriptions.TryGetValue(gid, out GlyphDescription description))
            {
                (uint offset, uint length) = loca.GetOffestAndLengthFromGlyphId(gid);
                using FileStream stream = new FileStream(FileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
                GlyfTableLoader loader = new GlyfTableLoader();
                description = await loader.LoadGlyfData(stream, entry, offset, length);
                GlyfTable.Descriptions.AddOrUpdate(gid, description, (key, value) => value);
            }
            return description;
        }

        /// <summary>
        /// 获取字形
        /// </summary>
        /// <param name="c"></param>
        /// <param name="platformID"></param>
        /// <param name="encodingID"></param>
        /// <returns></returns>
        public async Task<Glyph> ReadGlyph(char c, ushort platformID = 3, ushort encodingID = 1)
        {
            var cmap = await ReadCmapTable();
            uint gId = cmap.GetGlyphId(platformID, encodingID, c);
            var glyph = await ReadGlyph(gId);
            return glyph;
        }
        /// <summary>
        /// 通过Gid获取字形
        /// </summary>
        /// <param name="gId"></param>
        /// <returns></returns>
        public async Task<Glyph> ReadGlyph(uint gId)
        {
            var loca = await ReadLocaTable();
            TableEntry entry = GetEntry(TableNames.Glyf);
            GlyfTableLoader loader = new GlyfTableLoader();
            if (!GlyfTable.Descriptions.TryGetValue(gId, out GlyphDescription description))
            {
                (uint offset, uint length) = loca.GetOffestAndLengthFromGlyphId(gId);
                using FileStream stream = new FileStream(FileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
                description = await loader.LoadGlyfData(stream, entry, offset, length);
                GlyfTable.Descriptions.AddOrUpdate(gId, description, (key, value) => value);
            }
            Glyph glyph = loader.LoadGlyph(description);
            glyph.GId = gId;
            return glyph;
        }
        /// <summary>
        /// 读取多个字形
        /// </summary>
        /// <param name="chars"></param>
        /// <param name="platformID"></param>
        /// <param name="encodingID"></param>
        /// <returns></returns>
        public async Task<Glyph[]> ReadGlyphs(char[] chars, ushort platformID = 3, ushort encodingID = 1)
        {
            var cmap = await ReadCmapTable();
            uint[] gIds = new uint[chars.Length];
            for(int i = 0; i < chars.Length; i++)
            {
                uint gId = cmap.GetGlyphId(platformID, encodingID, chars[i]);
                gIds[i] = gId;
            }
            Glyph[] glyphs = await ReadGlyphs(gIds);
            return glyphs;
        }
        /// <summary>
        /// 读取多个字形
        /// </summary>
        /// <param name="gIds"></param>
        /// <returns></returns>
        public async Task<Glyph[]> ReadGlyphs(uint[] gIds)
        {
            var loca = await ReadLocaTable();
            TableEntry entry = GetEntry(TableNames.Glyf);
            GlyfTableLoader loader = new GlyfTableLoader();
            Glyph[] glyphs = new Glyph[gIds.Length];
            using CountdownEvent @event = new CountdownEvent(gIds.Length);
            int index = -1;
            for (int i = 0; i < gIds.Length; i++)
            {
                _ = Task.Run(async () =>
                {
                    var local = Interlocked.Increment(ref index);
                    uint gId = gIds[local];
                    if (!GlyfTable.Descriptions.TryGetValue(gId, out GlyphDescription description))
                    {
                        (uint offset, uint length) = loca.GetOffestAndLengthFromGlyphId(gId);
                        using FileStream stream = new FileStream(FileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
                        description = await loader.LoadGlyfData(stream, entry, offset, length);
                        GlyfTable.Descriptions.AddOrUpdate(gId, description, (key, value) => value);
                    }
                    Glyph glyph = loader.LoadGlyph(description);
                    glyph.GId = gId;
                    glyphs[local] = glyph;
                    @event.Signal();
                });
            }
            @event.Wait();
            return glyphs;
        }
        /// <summary>
        /// 读取多个字形
        /// </summary>
        /// <param name="chars"></param>
        /// <param name="taskRange">每个线程处理字符数</param>
        /// <param name="platformID"></param>
        /// <param name="encodingID"></param>
        /// <returns></returns>
        public async Task<Glyph[]> ReadGlyphs(char[] chars, int taskRange, ushort platformID = 3, ushort encodingID = 1)
        {
            var cmap = await ReadCmapTable();
            int length = chars.Length;
            uint[] gIds = new uint[length];
            for(int i = 0; i < length; i++)
            {
                uint gId = cmap.GetGlyphId(platformID, encodingID, chars[i]);
                gIds[i] = gId;
            }
            Glyph[] glyphs = await ReadGlyphs(gIds, taskRange);
            return glyphs;
        }
        /// <summary>
        /// 读取多个字形
        /// </summary>
        /// <param name="gIds"></param>
        /// <param name="taskRange"></param>
        /// <returns></returns>
        public async Task<Glyph[]> ReadGlyphs(uint[] gIds, int taskRange)
        {
            var loca = await ReadLocaTable();
            var cmap = await ReadCmapTable();
            TableEntry entry = GetEntry(TableNames.Glyf);
            GlyfTableLoader loader = new GlyfTableLoader();
            int length = gIds.Length;
            Glyph[] glyphs = new Glyph[length];
            int range = length / taskRange;
            range = length % taskRange == 0 ? range : range + 1;
            int[] books = new int[range];
            int index = -1;
            for (int i = 0; i < range; i++) books[i] = i * taskRange;
            if(range <= 1)
            {
                int start = books[0];
                int end = start + taskRange;
                if (end > length) end -= (end - length);
                using FileStream stream = new FileStream(FileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
                for (int m = start; m < end; m++)
                {
                    uint gId = gIds[m];
                    if (!GlyfTable.Descriptions.TryGetValue(gId, out GlyphDescription description))
                    {
                        (uint offset, uint gLength) = loca.GetOffestAndLengthFromGlyphId(gId);
                        description = await loader.LoadGlyfData(stream, entry, offset, gLength);
                        GlyfTable.Descriptions.AddOrUpdate(gId, description, (key, value) => value);
                    }
                    Glyph glyph = loader.LoadGlyph(description);
                    glyph.GId = gId;
                    glyphs[m] = glyph;
                }
            }
            else
            {
                using CountdownEvent @event = new CountdownEvent(range);
                for (int i = 0; i < range; i++)
                {
                    _ = Task.Run(async () =>
                    {
                        var local = Interlocked.Increment(ref index);
                        int start = books[local];
                        int end = start + taskRange;
                        if (end > length) end -= (end - length);
                        try
                        {
                            using FileStream stream = new FileStream(FileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
                            for (int m = start; m < end; m++)
                            {
                                uint gId = gIds[m];
                                if (!GlyfTable.Descriptions.TryGetValue(gId, out GlyphDescription description))
                                {
                                    (uint offset, uint gLength) = loca.GetOffestAndLengthFromGlyphId(gId);
                                    description = await loader.LoadGlyfData(stream, entry, offset, gLength);
                                    GlyfTable.Descriptions.AddOrUpdate(gId, description, (key, value) => value);
                                }
                                Glyph glyph = loader.LoadGlyph(description);
                                glyph.GId = gId;
                                glyphs[m] = glyph;
                            }
                        }
                        finally
                        {
                            @event.Signal();
                        }
                    });
                }
                @event.Wait();
            }
            return glyphs;
        }

        /// <summary>
        /// 销毁资源
        /// </summary>
        public void Dispose()
        {
            if (!Volatile.Read(ref _IsDisable))
            {
                Volatile.Write(ref _IsDisable, true);
                _SemaphoreSlim.Dispose();
                _Tables.Clear();
            }
        }

        /// <summary>
        /// 获取入口
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private TableEntry GetEntry(string name)
        {
            foreach (var item in OffsetTable.Entries)
            {
                if (item.Tag == name) return item;
            }
            throw new Exception($"Cannot found entry table {name} in this file");
        }
    }
}
